package com.ydlclass;

import com.ydlclass.annotation.YrpcApi;
import com.ydlclass.channelHandler.handler.MethodCallHandler;
import com.ydlclass.channelHandler.handler.YrpcRequestDecoder;
import com.ydlclass.channelHandler.handler.YrpcResponseEncoder;
import com.ydlclass.core.HeartbeatDetector;
import com.ydlclass.discovery.Registry;
import com.ydlclass.discovery.RegistryConfig;
import com.ydlclass.loadbnlancer.LoadBalancer;
import com.ydlclass.loadbnlancer.impl.ConsistentHashBalancer;
import com.ydlclass.loadbnlancer.impl.MinimumResponseTimeLoadBalancer;
import com.ydlclass.loadbnlancer.impl.RoundRobinLoadBalancer;
import com.ydlclass.transport.message.YrpcRequest;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author Knife
 */

@Slf4j
public class YrpcBootstrap {
    public static final int PORT = 8090;
    //YrpcBootstrap 是一个单例，我们希望每个应用程序只有一个实例

    private static YrpcBootstrap yrpcBootstrap = new YrpcBootstrap();

    // 定义相关的基础配置
    private String applicationName = "default";
    private RegistryConfig registryConfig;
    private ProtocolConfig protocolConfig;
    public static final IdGenerator ID_GENERATOR = new IdGenerator(1,2);
    // todo: 待处理
    private Registry registry;

    public  static LoadBalancer LOAD_BALANCE_STRATEGY;

    public static String SERIALIZE_TYPE = null;
    public static String COMPRESSOR_TYPE = null;

    public static final ThreadLocal<YrpcRequest> THREAD_LOCAL = new ThreadLocal<>();

    // 连接的缓存 : 在用类作为key的时候一定要看他有没有重写equals和toString方法
    public final static Map<InetSocketAddress, Channel> CHANNEL_CACHE = new ConcurrentHashMap<>(16);
    public final static TreeMap<Long, Channel> ANSWER_TIME_CHANNEL_CACHE = new TreeMap<>();

    //维护已经发布并且暴露出去的服务列表
    public static Map<String,ServiceConfig<?>> SERVERS_LIST = new HashMap<>(16);

    // 定义全局对外挂起的completableFuture
    public final static Map<Long,CompletableFuture<Object>> PENDING_REQUEST = new ConcurrentHashMap<>(128);

    /**
     * 维护一个zookeeper实例
     */
    //private ZooKeeper zooKeeper;


    private YrpcBootstrap() {
        // 构造启动引导，需要做一些初始化的事情
    }

    public static YrpcBootstrap getInstance() {

        return yrpcBootstrap;
    }

    /**
     * 用来定义当前应用的名字
     * @param appName
     * @return this当前实例
     */
    public YrpcBootstrap application(String appName) {
        this.applicationName = appName;
        return this;
    }

    /**
     * 用来配置一个注册中心
     * @param registryConfig
     * @return this 当前实例
     */
    public YrpcBootstrap registry(RegistryConfig registryConfig){
        // 维护一个zookeeper实例，这样写会将zookeeper和当前工程耦合
        // 我们其实想要以后扩展更多不同的实现

        // 尝试使用获取一个 registryConfig 注册中心，
        this.registry = registryConfig.getRegistry();
        // todo : 待处理
        YrpcBootstrap.LOAD_BALANCE_STRATEGY = new RoundRobinLoadBalancer();
        return this;
    }

    /**
     *  配置当前暴露的服务使用的协议
     * @param protocolConfig 协议的封装
     * @return
     */
    public YrpcBootstrap protocol(ProtocolConfig protocolConfig){
        this.protocolConfig = protocolConfig;
        if (log.isDebugEnabled()){
            log.debug("配置了当前工程使用了协议:{}",protocolConfig);
        }
        return this;
    }

    /**
     * ---------------------------服务提供方的相关api---------------------------------
     */



    /**
     * 服务的发布，将接口和对应的实现注入到注册中心
     * @param service 封装的独立的服务
     * @return this
     */
    public YrpcBootstrap publish(ServiceConfig<?> service) {
        // 抽象了注册中心的概念，使用注册中心的一个实现完成注册
        registry.register(service);
        // 1、 当服务调用方，通过接口、方法名、具体的方法参数列表发起调用，提供怎么知道是哪一个实现
        // (1) new 一个  ；   (2)Spring里面的beanFactory.getBean(Class)   (3)自己维护一个映射关系
        SERVERS_LIST.put(service.getInterfaceProvider().getName(),service);

        return this;
    }

    /**
     * 批量发布
     * @param service
     * @return
     */
    public YrpcBootstrap publish(List<ServiceConfig<?>> service) {
        for (ServiceConfig<?> serviceConfig : service) {
            this.publish(serviceConfig);
        }
        return this;
    }

    /**
     * 启动Netty服务
     */
    public void start() {

        NioEventLoopGroup boss = new NioEventLoopGroup(2);
        NioEventLoopGroup worker = new NioEventLoopGroup(10);

        ServerBootstrap bootstrap = new ServerBootstrap();
        try {
            ChannelFuture channelFuture = bootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            // 是核心，添加很多入站出站handler
                            ch.pipeline()
                                    .addLast(new LoggingHandler())
                                    .addLast(new YrpcRequestDecoder())
                                    // 根据请求进行方法调用
                                    .addLast(new MethodCallHandler())

                                    .addLast(new YrpcResponseEncoder());
                        }
                    })
                    .bind(PORT).sync();


            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                boss.shutdownGracefully().sync();
                worker.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }



    /**
     * ---------------------------服务调用方的相关api---------------------------------
     */

    /**
     *
     * @param reference
     */
    public YrpcBootstrap reference(ReferenceConfig<?> reference) {

        // todo 开启对这个服务的心跳检测
        HeartbeatDetector.detectHeartbeat(reference.getInterfaceRef().getName());
        // 在这个方法里我们是否可以拿到相关的配置项-注册中心
        // 配置reference，将来调用get方法时，方便生成代理对象

        // 1、突然发现，Reference需要一个注册中心
        reference.setRegistry(registry);
        return this;
    }

    /**
     * 配置序列化的方式
     * @param serialize 序列化的方式
     * @return
     */
    public YrpcBootstrap serialize(String serialize) {
        SERIALIZE_TYPE = serialize;
        if (log.isDebugEnabled()){
            log.debug("配置了当前工程使用了序列化方式:{}",SERIALIZE_TYPE);
        }
        return this;
    }

    /**
     * 配置压缩的方式
     * @param compressType
     * @return
     */
    public YrpcBootstrap compress(String compressType) {
        COMPRESSOR_TYPE = compressType;
        if (log.isDebugEnabled()){
            log.debug("配置了当前工程使用了压缩方式:{}",compressType);
        }
        return this;
    }

    public Registry getRegistry() {
        return registry;
    }

    /**
     * 批量扫描发布 服务
     * @param packageName
     * @return
     */
    public YrpcBootstrap scan(String packageName) {
        // 1、需要通过packageName 获取其下的所有类
        List<String> classNames = getAllClassNames(packageName);
        // 2、通过反射获取他的接口，构建实现
        List<Class<?>> classes = classNames.stream().map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }).filter(clazz -> clazz.getAnnotation(YrpcApi.class) != null)
                .collect(Collectors.toList());
        for (Class<?> aClass : classes) {
            Class<?>[] interfaces = aClass.getInterfaces();
            Object instance = null;
            try {
                instance = aClass.getConstructor().newInstance();
            }  catch (InstantiationException | IllegalAccessException |InvocationTargetException |NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
            for (Class<?> anInterface : interfaces) {
                ServiceConfig<Object> serviceConfig = new ServiceConfig<>();
                serviceConfig.setInterface(anInterface);
                serviceConfig.setRef(instance);
                if (log.isDebugEnabled()){
                    log.debug("已经通过包扫描将服务{}发布",anInterface);
                }
                // 3、发布
                publish(serviceConfig);
            }
        }
        return this;
    }

    private List<String> getAllClassNames(String packageName) {
        // 通过packageName获取绝对路径
        // com.ydlclass.xxx.yyyy  ->>  E:// xxx/yyy/dsss/com/ydlclass/xxx/yyyy.class
        String basePath = packageName.replaceAll("\\.", "/");

        URL url = ClassLoader.getSystemClassLoader().getResource(basePath);
        if (url == null){
            throw new RuntimeException("没有找到包,包扫描发现路径不存在");
        }
        String absolutePath = url.getPath();

        List<String> classNames = new ArrayList<>();

        classNames = recursionFile(absolutePath,classNames,basePath);
        return classNames;
    }

    private List<String> recursionFile(String absolutePath, List<String> classNames ,String basePath) {
        // 获取文件
        File file = new File(absolutePath);
        // 判断文件是否是文件夹
        if (file.isDirectory()){
            // 找到文件夹下面的所有文件
            File[] files = file.listFiles(pathname -> pathname.isDirectory() || pathname.getPath().contains(".class"));
            if (files == null || files.length == 0){
                return classNames;
            }
            for (File child : files) {
                if (child.isDirectory()){
                    recursionFile(child.getAbsolutePath(),classNames,basePath);
                } else {
                    //M:\RPC\dubbo\dwjrpc\dwjrpc-framework\dwjrpc-core\target\classes\com\ydlclass\channelHandler\ConsumerChannelInitializer.class
                    String className = getClassNameByAbsolutePath(child.getAbsolutePath(),basePath);
                    classNames.add(className);
                }
            }
        } else {
            // 是文件
            classNames.add(getClassNameByAbsolutePath(absolutePath,basePath));
        }
        return classNames;
    }

    private String getClassNameByAbsolutePath(String absolutePath,String basePath) {
        //M:\RPC\dubbo\dwjrpc\dwjrpc-framework\dwjrpc-core\target\classes\com\ydlclass\channelHandler\ConsumerChannelInitializer.class
        //  com\ydlclass\channelHandler\ConsumerChannelInitializer.class  -->com.ydlclass.channelHandler.ConsumerChannelInitializer.class
        //  com.ydlclass.channelHandler.ConsumerChannelInitializer
        String className = absolutePath
                .substring(absolutePath.indexOf(basePath.replaceAll("/","\\\\")))
                .replaceAll("\\\\","\\.");
        className = className.substring(0,className.indexOf(".class"));
        return className;
    }

    public static void main(String[] args) {
        List<String> classNames = YrpcBootstrap.getInstance().getAllClassNames("com.ydlclass");
        for (String className : classNames) {
            System.out.println(className);
        }
    }
}


